﻿using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Xml.Linq;

namespace DoNet.Rsa
{
    /// <summary>
    /// RSA密钥转换类
    /// </summary>
    public class RsaKeyConvert
    {
        /// <summary>
        /// pem公钥转换xml
        /// </summary>
        /// <param name="publicKey">pem公钥</param>
        /// <returns></returns>
        public static string PublicKeyPemToXml(string publicKey)
        {
            string rsaKey = string.Empty;
            publicKey = PemFormatHelper.PublicKeyFormat(publicKey);

            using (PemReader pr = new PemReader(new StringReader(publicKey)))
            {
                var rsaPara = pr.ReadObject() as RsaKeyParameters;

                if (rsaPara != null)
                {
                    XElement publicElement = new XElement("RSAKeyValue");
                    XElement pubmodulus = new XElement("Modulus", Convert.ToBase64String(rsaPara.Modulus.ToByteArrayUnsigned()));
                    XElement pubexponent = new XElement("Exponent", Convert.ToBase64String(rsaPara.Exponent.ToByteArrayUnsigned()));

                    publicElement.Add(pubmodulus);
                    publicElement.Add(pubexponent);

                    rsaKey = publicElement.ToString();
                }
                pr.Reader.Close();
            }
            return rsaKey;
        }

        /// <summary>
        /// xml公钥转换pem
        /// </summary>
        /// <param name="publicKey">xml公钥</param>
        /// <returns></returns>
        public static string PublicKeyXmlToPem(string publicKey)
        {
            string rsaKey = string.Empty;
            XElement root = XElement.Parse(publicKey);
            var modulus = root.Element("Modulus");
            var exponent = root.Element("Exponent");
            if (modulus != null && exponent != null)
            {
                RsaKeyParameters rsaKeyParameters = new RsaKeyParameters(false, new BigInteger(1, Convert.FromBase64String(modulus.Value)), new BigInteger(1, Convert.FromBase64String(exponent.Value)));

                using (StringWriter sw = new StringWriter())
                {
                    using (PemWriter pWrt = new PemWriter(sw))
                    {
                        pWrt.WriteObject(rsaKeyParameters);
                        pWrt.Writer.Close();
                    }
                    rsaKey = sw.ToString();
                }
            }
            return rsaKey;
        }

        /// <summary>
        /// Pkcs1私钥转换xml
        /// </summary>
        /// <param name="privateKey">Pkcs1私钥</param>
        /// <returns></returns>
        public static string PrivateKeyPkcs1ToXml(string privateKey)
        {
            return PrivateKeyPkcsToXml(privateKey, RsaKeyFormat.Pkcs1);
        }

        /// <summary>
        /// xml私钥转换Pkcs1
        /// </summary>
        /// <param name="privateKey">xml私钥</param>
        /// <returns></returns>
        public static string PrivateKeyXmlToPkcs1(string privateKey)
        {
            return PrivateKeyXmlToPkcs(privateKey, RsaKeyFormat.Pkcs1);
        }

        /// <summary>
        /// Pkcs8私钥转换xml
        /// </summary>
        /// <param name="privateKey">Pkcs8私钥</param>
        /// <returns></returns>
        public static string PrivateKeyPkcs8ToXml(string privateKey)
        {
            return PrivateKeyPkcsToXml(privateKey, RsaKeyFormat.Pkcs8);
        }

        /// <summary>
        /// xml私钥转换Pkcs8
        /// </summary>
        /// <param name="privateKey">xml私钥</param>
        /// <returns></returns>
        public static string PrivateKeyXmlToPkcs8(string privateKey)
        {
            return PrivateKeyXmlToPkcs(privateKey, RsaKeyFormat.Pkcs8);
        }

        /// <summary>
        /// Pkcs1私钥转换Pkcs8
        /// </summary>
        /// <param name="privateKey">Pkcs1私钥</param>
        /// <returns></returns>
        public static string PrivateKeyPkcs1ToPkcs8(string privateKey)
        {
            string rsaKey = string.Empty;
            privateKey = PemFormatHelper.Pkcs1PrivateKeyFormat(privateKey);
            using (PemReader pr = new PemReader(new StringReader(privateKey)))
            {
                var kp = pr.ReadObject() as AsymmetricCipherKeyPair;
                if (kp != null)
                {
                    using (StringWriter sw = new StringWriter())
                    {
                        using (PemWriter pw = new PemWriter(sw))
                        {
                            pw.WriteObject(new Pkcs8Generator(kp.Private));
                            pw.Writer.Close();
                        }
                        rsaKey = sw.ToString();
                    }
                }
                pr.Reader.Close();
            }
            return rsaKey;
        }

        /// <summary>
        /// Pkcs8私钥转换Pkcs1
        /// </summary>
        /// <param name="privateKey">Pkcs8私钥</param>
        /// <returns></returns>
        public static string PrivateKeyPkcs8ToPkcs1(string privateKey)
        {
            string rsaKey = string.Empty;
            privateKey = PemFormatHelper.Pkcs8PrivateKeyFormat(privateKey);
            using (PemReader pr = new PemReader(new StringReader(privateKey)))
            {
                var kp = pr.ReadObject() as RsaPrivateCrtKeyParameters;
                var keyParameter = PrivateKeyFactory.CreateKey(PrivateKeyInfoFactory.CreatePrivateKeyInfo(kp));

                using (StringWriter sw = new StringWriter())
                {
                    using (PemWriter pw = new PemWriter(sw))
                    {
                        pw.WriteObject(keyParameter);
                        pw.Writer.Close();
                    }
                    rsaKey = sw.ToString();
                }
                pr.Reader.Close();
            }
            return rsaKey;
        }

        /// <summary>
        /// Pem 指定密钥格式（Pkcs1，Pkcs8）的私钥转换xml
        /// </summary>
        /// <param name="privateKey">Pkcs私钥</param>
        /// <param name="pkcs">密钥格式 Pkcs1，Pkcs8</param>
        /// <returns></returns>
        public static string PrivateKeyPkcsToXml(string privateKey, RsaKeyFormat pkcs = RsaKeyFormat.Pkcs1)
        {
            string rsaKey = string.Empty;
            var param = GetPrivateKeyParam(privateKey, pkcs);
            if (param != null)
            {
                RSAParameters rsaParam = (RSAParameters)param;

                #region 方法一
                //RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
                //rsa.ImportParameters(rsaParam);
                //using (StringWriter sw = new StringWriter())
                //{
                //    sw.Write(rsa.ToXmlString(true));
                //    rsaKey = sw.ToString();
                //}
                #endregion 方法一

                #region 方法二
                var modulus = rsaParam.Modulus;
                var exponent = rsaParam.Exponent;
                var p = rsaParam.P;
                var q = rsaParam.Q;
                var dp = rsaParam.DP;
                var dq = rsaParam.DQ;
                var inverseQ = rsaParam.InverseQ;
                var d = rsaParam.D;
                if (modulus != null && exponent != null && p != null && q != null && dp != null && dq != null && inverseQ != null && d != null)
                {
                    XElement privatElement = new XElement("RSAKeyValue");
                    XElement primodulus = new XElement("Modulus", Convert.ToBase64String(modulus));
                    XElement priexponent = new XElement("Exponent", Convert.ToBase64String(exponent));
                    XElement prip = new XElement("P", Convert.ToBase64String(p));
                    XElement priq = new XElement("Q", Convert.ToBase64String(q));
                    XElement pridp = new XElement("DP", Convert.ToBase64String(dp));
                    XElement pridq = new XElement("DQ", Convert.ToBase64String(dq));
                    XElement priinverseQ = new XElement("InverseQ", Convert.ToBase64String(inverseQ));
                    XElement prid = new XElement("D", Convert.ToBase64String(d));

                    privatElement.Add(primodulus);
                    privatElement.Add(priexponent);
                    privatElement.Add(prip);
                    privatElement.Add(priq);
                    privatElement.Add(pridp);
                    privatElement.Add(pridq);
                    privatElement.Add(priinverseQ);
                    privatElement.Add(prid);

                    rsaKey = privatElement.ToString();
                }
                #endregion 方法二
            }
            return rsaKey;
        }

        /// <summary>
        /// Pem格式私钥转换xml
        /// </summary>
        /// <param name="privateKey">Pkcs私钥</param>
        /// <returns></returns>
        public static string PrivateKeyPkcsToXml(string privateKey)
        {
            RsaKeyFormat pkcs = RsaKeyFormat.Pkcs1;
            if (privateKey.StartsWith(PemFormatHelper.Pkcs8PrivateKeyBegin) && privateKey.EndsWith(PemFormatHelper.Pkcs8PrivateKeyEnd))
            {
                pkcs = RsaKeyFormat.Pkcs8;
            }
            return PrivateKeyPkcsToXml(privateKey, pkcs);
        }

        /// <summary>
        /// xml私钥转换Pkcs
        /// </summary>
        /// <param name="privateKey">xml私钥</param>
        /// <param name="pkcs">密钥格式 Pkcs1，Pkcs8</param>
        /// <returns></returns>
        public static string PrivateKeyXmlToPkcs(string privateKey, RsaKeyFormat pkcs = RsaKeyFormat.Pkcs1)
        {
            string rsaKey = string.Empty;
            XElement root = XElement.Parse(privateKey);
            var modulus = root.Element("Modulus");
            var exponent = root.Element("Exponent");
            var p = root.Element("P");
            var q = root.Element("Q");
            var dp = root.Element("DP");
            var dq = root.Element("DQ");
            var inverseQ = root.Element("InverseQ");
            var d = root.Element("D");

            if (modulus != null && exponent != null && p != null && q != null && dp != null && dq != null && inverseQ != null && d != null)
            {
                RsaPrivateCrtKeyParameters privateKeyParam = new RsaPrivateCrtKeyParameters(
                    new BigInteger(1, Convert.FromBase64String(modulus.Value)),
                    new BigInteger(1, Convert.FromBase64String(exponent.Value)),
                    new BigInteger(1, Convert.FromBase64String(d.Value)),
                    new BigInteger(1, Convert.FromBase64String(p.Value)),
                    new BigInteger(1, Convert.FromBase64String(q.Value)),
                    new BigInteger(1, Convert.FromBase64String(dp.Value)),
                    new BigInteger(1, Convert.FromBase64String(dq.Value)),
                    new BigInteger(1, Convert.FromBase64String(inverseQ.Value)));

                using (StringWriter sw = new StringWriter())
                {
                    using (PemWriter pw = new PemWriter(sw))
                    {
                        if (pkcs == RsaKeyFormat.Pkcs1)
                        {
                            pw.WriteObject(privateKeyParam);
                        }
                        else if (pkcs == RsaKeyFormat.Pkcs8)
                        {
                            pw.WriteObject(new Pkcs8Generator(privateKeyParam));
                        }
                        pw.Writer.Close();
                    }
                    rsaKey = sw.ToString();
                }
            }
            return rsaKey;
        }

        /// <summary>
        /// 根据Pkcs私钥获取RSA参数
        /// </summary>
        /// <param name="privateKey">Pkcs私钥</param>
        /// <param name="pkcs">密钥格式 Pkcs1，Pkcs8</param>
        /// <returns></returns>
        public static RSAParameters? GetPrivateKeyParam(string privateKey, RsaKeyFormat pkcs = RsaKeyFormat.Pkcs1)
        {
            if (pkcs == RsaKeyFormat.Pkcs1)
            {
                privateKey = PemFormatHelper.Pkcs1PrivateKeyFormat(privateKey);
                using (PemReader pr = new PemReader(new StringReader(privateKey)))
                {
                    var asymmetricCipherKeyPair = pr.ReadObject() as AsymmetricCipherKeyPair;
                    if (asymmetricCipherKeyPair != null)
                    {
                        RsaPrivateCrtKeyParameters rsaPrivateCrtKeyParameters = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(PrivateKeyInfoFactory.CreatePrivateKeyInfo(asymmetricCipherKeyPair.Private));

                        var rsaParam = new RSAParameters();
                        rsaParam.Modulus = rsaPrivateCrtKeyParameters.Modulus.ToByteArrayUnsigned();
                        rsaParam.Exponent = rsaPrivateCrtKeyParameters.PublicExponent.ToByteArrayUnsigned();
                        rsaParam.P = rsaPrivateCrtKeyParameters.P.ToByteArrayUnsigned();
                        rsaParam.Q = rsaPrivateCrtKeyParameters.Q.ToByteArrayUnsigned();
                        rsaParam.DP = rsaPrivateCrtKeyParameters.DP.ToByteArrayUnsigned();
                        rsaParam.DQ = rsaPrivateCrtKeyParameters.DQ.ToByteArrayUnsigned();
                        rsaParam.InverseQ = rsaPrivateCrtKeyParameters.QInv.ToByteArrayUnsigned();
                        rsaParam.D = rsaPrivateCrtKeyParameters.Exponent.ToByteArrayUnsigned();
                        return rsaParam;
                    }
                    pr.Reader.Close();
                }
            }
            else if (pkcs == RsaKeyFormat.Pkcs8)
            {
                privateKey = PemFormatHelper.Pkcs8PrivateKeyFormatRemove(privateKey);
                RsaPrivateCrtKeyParameters privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKey));

                var rsaParam = new RSAParameters();
                rsaParam.Modulus = privateKeyParam.Modulus.ToByteArrayUnsigned();
                rsaParam.Exponent = privateKeyParam.PublicExponent.ToByteArrayUnsigned();
                rsaParam.P = privateKeyParam.P.ToByteArrayUnsigned();
                rsaParam.Q = privateKeyParam.Q.ToByteArrayUnsigned();
                rsaParam.DP = privateKeyParam.DP.ToByteArrayUnsigned();
                rsaParam.DQ = privateKeyParam.DQ.ToByteArrayUnsigned();
                rsaParam.InverseQ = privateKeyParam.QInv.ToByteArrayUnsigned();
                rsaParam.D = privateKeyParam.Exponent.ToByteArrayUnsigned();
                return rsaParam;
            }
            return null;
        }

        /// <summary>
        /// 根据Pkcs私钥获取RSA参数
        /// </summary>
        /// <param name="publicKey">Pkcs私钥</param>
        /// <returns></returns>
        public static RSAParameters? GetPublicKeyParam(string publicKey)
        {
            publicKey = PemFormatHelper.PublicKeyFormat(publicKey);
            using (PemReader pr = new PemReader(new StringReader(publicKey)))
            {
                var rsaKey = pr.ReadObject() as RsaKeyParameters;
                if (rsaKey != null)
                {
                    var rsaParam = new RSAParameters();
                    rsaParam.Modulus = rsaKey.Modulus.ToByteArrayUnsigned();
                    rsaParam.Exponent = rsaKey.Exponent.ToByteArrayUnsigned();
                    return rsaParam;
                }
                pr.Reader.Close();
            }
            return null;
        }
    }
}